/************************************************************************
 * SMLib - State Model Library
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     
 * Description  :     
 * Change log   :     
 * To Do        :
 ************************************************************************/

using System;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Threading;
/***************************************************
 * The communication protocol:
 *
 * It uses a TCP communication and the format of packets between wrapper and
 * wrapper proxy is as follow:
 * 
 *  ---------------------------------------------------------
 * | VER (1byte) | TYPE (1byte) | Size (2byes) | content (optional)...
 *  ---------------------------------------------------------
 *
 * VER: should be 1.
 * 
 * TYPE:
 *    RESET   = 1;
 * 		after recieving a RESET, the wrapper proxy expects an error/OK packet.
 *    INPUT   = 102;
 * 	
 *    OUTPUT  = 103;
 * 		This packet is sent by the wrapper into the wrapper proxy.
 *    OK      = 4;
 * 		This is the acknowledment by wrapper [sent after reset]
 *    NOK     = 5;
 * 		This is the error (nacknowledgement) by the wrapper
 *    END     = 6; 
 * 		To finish the wrapper, after learning, the wrapper proxy
 * 		sends an END packet.
 *    UNKNOWN_ERROR  = 7;
 * 		This is the error (nacknowledgement) by the wrapper where the error is unknows
 *    LIST_INPUT_REQ  = 8;
 * 		After sending this packet, the wrapper proxy expect the LIST_OF_INPUTS packet.
 *    LIST_OF_INPUTS  = 109;
 * 		list of all input symbols separated with ";"
 * 
 * 
 * Note that all packets where their type > 100 could have content and
 * for other packets, there is no content and size is always 0.
 *******************************************************************/
namespace SMLib
{
	/// <summary>
	/// Recieve packet.
	/// </summary>
	public delegate void PacketReciever(TcpHelper.PACKET pack);

	/// <summary>
	/// TCP helper
	/// </summary>
	public class TcpHelper
	{
		public class PACKET
		{
			public const int TYPE_RESET   = 1; 
 			public const int TYPE_INPUT   = 102;
 			public const int TYPE_OUTPUT  = 103;

 			public const int TYPE_OK      = 4;
 			public const int TYPE_NOK     = 5;
 			public const int TYPE_END     = 6; 
 			public const int TYPE_UNKNOWN_ERROR  = 7;
 			public const int TYPE_LIST_INPUT_REQ  = 8;
 			public const int TYPE_LIST_OF_INPUTS  = 109;

			//* for IA
			/*
			public const int ANY_OUTPUT = 104;
			public const int  YES = 9;// in response to ANY_OUTPUT
			public const int  NO = 10;// in response to ANY_OUTPUT
			*/


			const byte VER=1;
			public byte TYPE;
			public string CONTENT;

			public byte[] ToBytes()
			{
				byte [] buff;
				//* without content
				if(TYPE<100)
				{
					buff=new byte[4];
					buff[0]=VER;
					buff[1]=TYPE;
					buff[2]=buff[3]=0;					
				}
				else
				{
					buff=Pack(CONTENT,4);
					buff[0]=VER;
					buff[1]=TYPE;
					//Console.WriteLine("buff.Length="+buff.Length);
					buff[2]=(byte)((buff.Length-4)/256);
					buff[3]=(byte)((buff.Length-4)%256);
				}
				return buff;
			}

			public static PACKET FromBytes(byte[] b)
			{
				PACKET p=new PACKET();
				p.TYPE=b[1];
				p.CONTENT=Unpack(b,4);
				return p;
			}
			
			
			public static PACKET FromBytes(byte[] b,int contentSize)
			{
				PACKET p=new PACKET();
				p.TYPE=b[1];
				p.CONTENT=Unpack(b,4,contentSize-4);
				return p;
			}

			static byte[] Pack(string str,int offset=0)
        	{
            	byte[] message = System.Text.Encoding.ASCII.GetBytes(str);
				byte[] packet = new byte[offset+message.Length];
            	System.Buffer.BlockCopy(message, 0, packet, offset, message.Length);
            	return packet;
        	}


        	static string Unpack(byte[] bytes, int offset=0)
        	{
            	return System.Text.Encoding.ASCII.GetString(bytes,offset,bytes.Length-offset);
        	}
			
			static string Unpack(byte[] bytes, int offset=0,int size=-1)
        	{
				if(size==-1)
					size=bytes.Length-offset;
				
            	return System.Text.Encoding.ASCII.GetString(bytes,offset,size);
        	}
			
			public override string ToString ()
			{
				return "Type: "+TYPE+" Content:"+CONTENT;
			}
		}

		const int maxPacketSize=1000;
		int port;
        string server;

		TcpClient inputClient, outputClient;
        NetworkStream inputStream, outputStream;

		bool hasIndependentOutput;
		PacketReciever recHandler;
		Thread oThread;

		/// <summary>
		/// Initializes a new instance of the <see cref="SMLib.TcpHelper"/> class.
		/// </summary>
		/// <param name='server'>
		/// Server adress
		/// </param>
		/// <param name='port'>
		/// Port of the server
		/// </param>
		public TcpHelper (string server, int port, bool hasIndependentOutput=false, PacketReciever recHandler=null)
		{
            this.server = server;
            this.port = port;
			this.hasIndependentOutput=hasIndependentOutput;
			this.recHandler=recHandler;
		}

		/// <summary>
		/// The Reciever thread.
		/// </summary>
		void RecieverThread()
		{
			//Console.WriteLine ("Rec started...");
			byte [] buff={0,0,0,0};
			//Console.WriteLine ("R=" + recHandler);
			while (true) {
				//Thread.Sleep (100000);
				DoBlockingread (outputStream, buff, 0, 4, int.MaxValue);

				int size = (buff [2] * 256 + buff [3]);
				//Console.Write("Recieved "+numberOfbytes+" bytes. Buff:"+buff[0]+" "+buff[1]+" "+buff[2]+" "+buff[3]);
				//Console.WriteLine("Expected size="+size);
				byte[] buff2 = new byte[4 + size];
				buff2 [0] = buff [0];
				buff2 [1] = buff [1];
				buff2 [2] = buff [2];
				buff2 [3] = buff [3];			              
				if (size != 0) {
					//inputStream.Read(buff2,4,size);
					DoBlockingread (outputStream, buff2, 4, size, 3000);
				}
				//Console.WriteLine("Done!");
				PACKET r = PACKET.FromBytes (buff2);

				//Console.Write ("O_"+r.CONTENT+".");

				//* in the end, it recieves an empty packet!
				if (r.CONTENT == "") {
					break;
				}

				recHandler (r);
			}
			outputStream.Close();
		}

		/// <summary>
        /// Initialize the connection
        /// </summary>
        public void Init()
        {
            try
            {
                inputClient = new TcpClient(server, port);
                inputStream = inputClient.GetStream();
				//Console.WriteLine("Connected successfuly!");
				if(hasIndependentOutput)
				{
                	outputClient = new TcpClient(server, port + 1);
               		outputStream = outputClient.GetStream();
		        	oThread = new Thread(new ThreadStart(RecieverThread));
			      	oThread.Start();
				}
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

		const int resetTime=2000;
		const int waitForEventTime=3000;
		
		private int DoBlockingread(NetworkStream stream,byte[] buff,int offset,int size,int timeout)
		{
			//Console.WriteLine ("Blocking " + size);
			DateTime startTime=DateTime.Now;
			int totalCount=0;
			while(true)
			{
				int count=stream.Read(buff, offset+totalCount, size-totalCount);
				//Console.WriteLine ("Read byte: " + count);
				totalCount+=count;
				if(totalCount==size)
					return totalCount;
				
				DateTime currentTime=DateTime.Now;
				TimeSpan sp=currentTime-startTime;
				if(sp.Milliseconds>timeout)
					return totalCount;
				else
					Thread.Sleep(10);				
			}			
		}
		
		/// <summary>
		/// Sends the packet
		/// <remarks>We assume that for each request, there is an answer</remarks>
		/// </summary>
		/// <param name='p'>
		/// The packet to be send
		/// </param>
		/// <returns>
		/// The packet recieved in answer
		/// </returns>
		public PACKET SendPacket(PACKET p)
		{
			//Console.WriteLine ("send pack1!");

			//* send
            byte[] buff = p.ToBytes();
			//Console.WriteLine("Sending a packet:"+buff[0]+" "+buff[1]+" "+buff[2]+" "+buff[3]);
            inputStream.Write(buff, 0, buff.Length);
            inputClient.ReceiveTimeout = 5000;
            //* and we check the result (the command accepted or no!
			//Console.WriteLine("Waiting for reply..");
			//
			//Console.WriteLine ("send pack2!");

			//Thread.Sleep(1000);
			//int numberOfbytes=inputStream.Read(buff, 0, 4);
			//int numberOfbytes=
			DoBlockingread(inputStream,buff, 0, 4, 3000);

			//Console.WriteLine ("send pack3!");
			
			int size=(buff[2]*256+buff[3]);
			//Console.Write("Recieved "+numberOfbytes+" bytes. Buff:"+buff[0]+" "+buff[1]+" "+buff[2]+" "+buff[3]);
			//Console.WriteLine("Expected size="+size);
			byte[] buff2=new byte[4+size];
			buff2[0]=buff[0];buff2[1]=buff[1];buff2[2]=buff[2];buff2[3]=buff[3];			              
			if(size!=0)
			{
				//inputStream.Read(buff2,4,size);
				DoBlockingread(inputStream,buff2, 4, size,3000);
			}
			//Console.WriteLine("Done!");
			PACKET r=PACKET.FromBytes(buff2);
			//Console.WriteLine("Recived Packet:"+r.ToString());
			return r;
		}

		/*

		public PACKET LastRecieviedOutput()
		{
			PACKET p;
			mx.WaitOne ();
			if (recievedPackets.Count == 0)
				p = null;
			else {
				p = recievedPackets [0];
				recievedPackets.RemoveAt (0);
			}
			mx.ReleaseMutex ();
		}
		*/

		public void Close()
        {
			byte[] buff = {0,PACKET.TYPE_END,0,0};
			//Console.WriteLine("Sending a packet:"+buff[0]+" "+buff[1]+" "+buff[2]+" "+buff[3]);
            inputStream.Write(buff, 0, buff.Length);
			Thread.Sleep(500);
            inputStream.Close();
            inputClient.Close();

			if(this.hasIndependentOutput)
			{
				oThread.Abort();
				outputStream.Close();
				outputClient.Close();
			}
        }
	}
}

